// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

extern "C" {
#include <X11/Xlib.h>
}

#include "ui/gl/gl_context_glx.h"

#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/numerics/safe_conversions.h"
#include "base/trace_event/trace_event.h"
#include "ui/gl/GL/glextchromium.h"
#include "ui/gl/gl_bindings.h"
#include "ui/gl/gl_implementation.h"
#include "ui/gl/gl_surface_glx.h"

namespace gfx {

GLContextGLX::GLContextGLX(GLShareGroup* share_group)
    : GLContextReal(share_group)
    , context_(nullptr)
    , display_(nullptr)
{
}

XDisplay* GLContextGLX::display()
{
    return display_;
}

bool GLContextGLX::Initialize(
    GLSurface* compatible_surface, GpuPreference gpu_preference)
{
    display_ = static_cast<XDisplay*>(compatible_surface->GetDisplay());

    GLXContext share_handle = static_cast<GLXContext>(
        share_group() ? share_group()->GetHandle() : nullptr);

    if (GLSurfaceGLX::IsCreateContextSupported()) {
        DVLOG(1) << "GLX_ARB_create_context supported.";
        std::vector<int> attribs;
        if (GLSurfaceGLX::IsCreateContextRobustnessSupported()) {
            DVLOG(1) << "GLX_ARB_create_context_robustness supported.";
            attribs.push_back(GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB);
            attribs.push_back(GLX_LOSE_CONTEXT_ON_RESET_ARB);
        }
        attribs.push_back(0);
        context_ = glXCreateContextAttribsARB(
            display_,
            static_cast<GLXFBConfig>(compatible_surface->GetConfig()),
            share_handle,
            True,
            &attribs.front());
        if (!context_) {
            LOG(ERROR) << "Failed to create GL context with "
                       << "glXCreateContextAttribsARB.";
            return false;
        }
    } else {
        DVLOG(1) << "GLX_ARB_create_context not supported.";
        context_ = glXCreateNewContext(
            display_,
            static_cast<GLXFBConfig>(compatible_surface->GetConfig()),
            GLX_RGBA_TYPE,
            share_handle,
            True);
        if (!context_) {
            LOG(ERROR) << "Failed to create GL context with glXCreateNewContext.";
            return false;
        }
    }
    DCHECK(context_);
    DVLOG(1) << "  Successfully allocated "
             << (compatible_surface->IsOffscreen() ? "offscreen" : "onscreen")
             << " GL context with LOSE_CONTEXT_ON_RESET_ARB";

    DVLOG(1) << (compatible_surface->IsOffscreen() ? "Offscreen" : "Onscreen")
             << " context was "
             << (glXIsDirect(display_,
                     static_cast<GLXContext>(context_))
                        ? "direct"
                        : "indirect")
             << ".";

    return true;
}

void GLContextGLX::Destroy()
{
    if (context_) {
        glXDestroyContext(display_,
            static_cast<GLXContext>(context_));
        context_ = nullptr;
    }
}

bool GLContextGLX::MakeCurrent(GLSurface* surface)
{
    DCHECK(context_);
    if (IsCurrent(surface))
        return true;

    ScopedReleaseCurrent release_current;
    TRACE_EVENT0("gpu", "GLContextGLX::MakeCurrent");
    if (!glXMakeContextCurrent(
            display_,
            reinterpret_cast<GLXDrawable>(surface->GetHandle()),
            reinterpret_cast<GLXDrawable>(surface->GetHandle()),
            static_cast<GLXContext>(context_))) {
        LOG(ERROR) << "Couldn't make context current with X drawable.";
        Destroy();
        return false;
    }

    // Set this as soon as the context is current, since we might call into GL.
    SetRealGLApi();

    SetCurrent(surface);
    if (!InitializeDynamicBindings()) {
        Destroy();
        return false;
    }

    if (!surface->OnMakeCurrent(this)) {
        LOG(ERROR) << "Could not make current.";
        Destroy();
        return false;
    }

    release_current.Cancel();
    return true;
}

void GLContextGLX::ReleaseCurrent(GLSurface* surface)
{
    if (!IsCurrent(surface))
        return;

    SetCurrent(nullptr);
    if (!glXMakeContextCurrent(display_, 0, 0, 0))
        LOG(ERROR) << "glXMakeCurrent failed in ReleaseCurrent";
}

bool GLContextGLX::IsCurrent(GLSurface* surface)
{
    bool native_context_is_current = glXGetCurrentContext() == static_cast<GLXContext>(context_);

    // If our context is current then our notion of which GLContext is
    // current must be correct. On the other hand, third-party code
    // using OpenGL might change the current context.
    DCHECK(!native_context_is_current || (GetRealCurrent() == this));

    if (!native_context_is_current)
        return false;

    if (surface) {
        if (glXGetCurrentDrawable() != reinterpret_cast<GLXDrawable>(surface->GetHandle())) {
            return false;
        }
    }

    return true;
}

void* GLContextGLX::GetHandle()
{
    return context_;
}

void GLContextGLX::OnSetSwapInterval(int interval)
{
    DCHECK(IsCurrent(nullptr));
    if (HasExtension("GLX_EXT_swap_control") && g_driver_glx.fn.glXSwapIntervalEXTFn) {
        glXSwapIntervalEXT(
            display_,
            glXGetCurrentDrawable(),
            interval);
    } else if (HasExtension("GLX_MESA_swap_control") && g_driver_glx.fn.glXSwapIntervalMESAFn) {
        glXSwapIntervalMESA(interval);
    } else {
        if (interval == 0)
            LOG(WARNING) << "Could not disable vsync: driver does not "
                            "support GLX_EXT_swap_control";
    }
}

std::string GLContextGLX::GetExtensions()
{
    DCHECK(IsCurrent(nullptr));
    const char* extensions = GLSurfaceGLX::GetGLXExtensions();
    if (extensions) {
        return GLContext::GetExtensions() + " " + extensions;
    }

    return GLContext::GetExtensions();
}

bool GLContextGLX::WasAllocatedUsingRobustnessExtension()
{
    return GLSurfaceGLX::IsCreateContextRobustnessSupported();
}

GLContextGLX::~GLContextGLX()
{
    Destroy();
}

} // namespace gfx
